﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;

namespace _1241108_1241150
{
    public class ItemDefine
    {
        public string Lop;
        public int soLan = 0;
        public double tongKC = 0;
    }

    public class KNN
    {
       
        private DataTable tapHuanLuyen = new DataTable();
        private DataTable tapThuNghiem = new DataTable();

        private DataTable tapHuanLuyenChuanHoa = new DataTable();
        private DataTable tapThuNghiemChuanHoa = new DataTable();

        private List<double> min = new List<double>();
        private List<double> max = new List<double>();
        private List<bool> kieuDuLieu = new List<bool>();
        private List<ItemDefine> ListItem = new List<ItemDefine>();


        private int k;

        public KNN(DataTable tapHuanLuyen, DataTable tapThuNghiem, List<bool> kieuDuLieu, int k)
        {
            this.tapHuanLuyen = tapHuanLuyen;
            this.tapThuNghiem = tapThuNghiem;
            this.kieuDuLieu = kieuDuLieu;
            this.k = k;
        }


        private void InitMinMax(DataTable dt)
        {
            for (int i = 0; i < dt.Columns.Count; i++)
                if (kieuDuLieu[i])
                {
                    double min = 0;
                    double max = 0;

                    for (int j = 0; j < dt.Rows.Count; j++)
                        if (dt.Rows[j][i] != DBNull.Value)
                        {
                            min = min > (double)dt.Rows[j][i] ? (double)dt.Rows[j][i] : min;
                            max = max < (double)dt.Rows[j][i] ? (double)dt.Rows[j][i] : max;
                        }

                    this.min.Add(min);
                    this.max.Add(max);
                }
                else
                {
                    this.min.Add(0);
                    this.max.Add(0);
                }
        }

        private DataTable ChuanHoaDataTable(DataTable dt)
        { 
            //lấy min max
            InitMinMax(dt);

            //chuẩn hóa table DT
            DataTable result = new DataTable();
            MyFunction myf = new MyFunction();

            myf.CopyDataTable(ref result, dt);

            for (int i = 0; i < dt.Columns.Count; i++)
                if (kieuDuLieu[i])
                    for (int j = 0; j < dt.Rows.Count; j++)
                        if (dt.Rows[j][i] != DBNull.Value)
                            result.Rows[j][i] = ChuanHoa((double)dt.Rows[j][i], this.min[i], this.max[i]);
            

            return result;
        }

        private double ChuanHoa(double dauVo, double min, double max)
        {
            double result = 0;

            result = (dauVo - min) / (max - min);

            return result;
        }

        public DataTable KNNAlg()
        {
            DataTable rs = this.tapThuNghiem;

            this.tapHuanLuyenChuanHoa = ChuanHoaDataTable(this.tapHuanLuyen);
            this.tapThuNghiemChuanHoa = ChuanHoaDataTable(this.tapThuNghiem);

            for (int i = 0; i < this.tapThuNghiem.Rows.Count; i++)
            {
                //KNN cho từng thuộc tính trong bảng thử nghiệm.
                DataRow dtr = this.tapThuNghiemChuanHoa.Rows[i];

                DataTable KhoangCachResult = TinhKhoanCach(dtr, this.tapHuanLuyenChuanHoa);
                
                // Sấp xếp bảng khoảng cách theo giá trị tăng dần.
                DataView dtv = KhoangCachResult.DefaultView;
                dtv.Sort = "KhoangCach ASC";
                KhoangCachResult = dtv.ToTable();

                //lấy ra K thằng.
                for (int j = 0; j < this.k; j++)
                {
                    ItemDefine item = new ItemDefine();

                    item.Lop = KhoangCachResult.Rows[j][1].ToString();
                    item.soLan = 1;
                    item.tongKC = (double)KhoangCachResult.Rows[j][0];

                    ThemPhanTu(item);
                }

                //Lớp khả thi nhất.
                string phanLopKhaThi = layRaPhanLopTotNhat(this.ListItem);

                for (int j = 0; j < rs.Rows.Count; j++)
                    for (int n = 0; n < rs.Columns.Count; n++)
                        if (rs.Rows[j][n] == DBNull.Value)
                            if (this.kieuDuLieu[j])
                                rs.Rows[j][n]=  double.Parse(phanLopKhaThi);
                            else
                                rs.Rows[j][n] = phanLopKhaThi;
            }
            return rs;
        }

        private void ThemPhanTu(ItemDefine item)
        {
            bool flag = true;

            for (int i = 0; i < ListItem.Count; i++)
                if (item.Lop == ListItem[i].Lop)
                {
                    flag = false;
                    ListItem[i].soLan++;
                    ListItem[i].tongKC += item.tongKC;
                }

            if (flag)
            {
                item.soLan = 1;
                ListItem.Add(item);
            }
        }

        private string layRaPhanLopTotNhat(List<ItemDefine> lstItem)
        {
            string phanlop = "";

            //sắp xếp lại giảm dần.
            for (int i = 0; i < ListItem.Count; i++)
            {
                for (int j = i; j < ListItem.Count; j++)
                {
                    if (ListItem[i].soLan < ListItem[j].soLan)
                    {
                        ItemDefine temp = ListItem[i];
                        ListItem[i] = ListItem[j];
                        ListItem[j] = temp;
                    }
                }
            }
            
            //kiểm xem có 2 phần tử có cùng độ phổ biến hông.
            int count = 0 ;
            double max = ListItem[0].soLan;

            for (int i = 0; i < ListItem.Count; i++)
            {
                if (ListItem[i].soLan == max)
                {
                    count++;
                }
            }

            if (count == 1)
                //Nếu chỉ có 1 thằng thì mình khong phải xét về khoảng cách
                phanlop = ListItem[0].Lop;
            else
            {
                //Nếu hơn 1 thằng thì mình tihn1 trung bình khoảng cách.
                double m = 0;

                for (int i = 0; i < count; i++)
                {
                    double temp = ListItem[i].tongKC / ListItem[i].soLan;

                    m = m > temp ? temp : m;
                }

                for (int i = 0; i < count; i++)
                {
                    double temp = ListItem[i].tongKC / ListItem[i].soLan;

                    if (temp == m)
                        phanlop = ListItem[i].Lop;

                }
            }

            return phanlop;
        }


        private DataTable TinhKhoanCach(DataRow dt1, DataTable dataTb)
        {
            DataTable dt = new DataTable();

            DataColumn cl1 = new DataColumn("KhoangCach", typeof(double));
            DataColumn cl2 = new DataColumn("phanLop", typeof(String));

            dt.Columns.Add(cl1);
            dt.Columns.Add(cl2);
            
            for (int i = 0; i < dataTb.Rows.Count; i++)
            {
                DataRow dtr = dt.NewRow();
                int viTri = 0;

                dtr[0] = khoangCach_Rows(dt1, dataTb.Rows[i], ref viTri);
                dtr[1] = this.tapHuanLuyen.Rows[i][viTri].ToString();

                dt.Rows.Add(dtr);
            }

            return dt;
        }

        /// <summary>
        /// Tính khoảng cách của 2 dòng
        /// </summary>
        /// <param name="dt1">Dòng chứa null</param>
        /// <param name="dt2">Dòng không chứa null (tập huấn luyện)</param>
        /// <param name="viTri">Vị trí phân lớp</param>
        /// <returns>KHoảng cách 2 dòng</returns>
        private double khoangCach_Rows(DataRow dt1, DataRow dt2, ref int viTri)
        {
            double rs = 0;

            //Xác định vị trí null
            for (int i = 0; i < dt1.ItemArray.Count(); i++)
            {
                if (dt1[i] == DBNull.Value)
                {
                    viTri = i;
                    break;
                }
                else if (this.kieuDuLieu[i])
                {
                    double kc = Math.Abs((double)dt1[i] - (double)dt2[i]);

                    rs += kc;
                }
                else
                    rs += 1;
            }

            return rs;
        }
    }
}
